package net.zoneland.knowledge.service.impl;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import net.zoneland.knowledge.dao.WorkApplyInfoMapper;
import net.zoneland.knowledge.enums.AttachTypeEnum;
import net.zoneland.knowledge.exceptions.WorkApplyException;
import net.zoneland.knowledge.model.LoginUserPojo;
import net.zoneland.knowledge.model.WorkApplyInfo;
import net.zoneland.knowledge.model.WorkApplyInfoAttachment;
import net.zoneland.knowledge.model.WorkSubTaskInfo;
import net.zoneland.knowledge.service.AttachmentService;
import net.zoneland.knowledge.utils.ExcelUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 附件相关服务.
 */
@Service
public class AttachmentServiceImpl implements AttachmentService {
    /**
     * 日志.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(AttachmentServiceImpl.class);
    /**
     * 工单信息数据接口.
     */
    @Autowired
    private WorkApplyInfoMapper workApplyInfoMapper;

    @Override
    public void initWorkApplyInfoAttachments(HttpServletRequest request, WorkApplyInfo info, LoginUserPojo user) {
        final MultipartHttpServletRequest multipartReq = (MultipartHttpServletRequest) request;
        //参考文档附件
        handleReferenceAttachments(info, user, multipartReq);

        //处理模板附件
        handleTemplateAttachments(info, user, multipartReq);

        //处理结果附件(上报附件)
        handleReportAttachments(info.getSubTasks(), user, multipartReq);
    }

    public void handleReferenceAttachments(WorkApplyInfo info, LoginUserPojo user, MultipartHttpServletRequest multipartReq) {
        final List<WorkApplyInfoAttachment> newUploadAttachments =
                getAttachmentsByParamName(multipartReq, AttachTypeEnum.REFERENCE, user);
        if (CollectionUtils.isNotEmpty(newUploadAttachments)) {
            final List<WorkApplyInfoAttachment> referenceAttachments =
                    ObjectUtils.defaultIfNull(info.getReferenceAttachments(), Lists.newArrayList());
            referenceAttachments.addAll(newUploadAttachments);
            info.setReferenceAttachments(referenceAttachments);
        }
    }

    public void handleTemplateAttachments(final WorkApplyInfo info,
                                          final LoginUserPojo user,
                                          final MultipartHttpServletRequest multipartReq) {
        handleAttachmentsForSubTaskByAttachType(info.getSubTasks(), user, multipartReq, AttachTypeEnum.TEMPLATE,
                WorkSubTaskInfo::setTemplateAttachments);
    }

    public void handleReportAttachments(final List<WorkSubTaskInfo> subTasks,
                                        final LoginUserPojo user,
                                        final MultipartHttpServletRequest multipartReq) {
        handleAttachmentsForSubTaskByAttachType(subTasks, user, multipartReq, AttachTypeEnum.RESULT,
                WorkSubTaskInfo::setReportAttachments);
    }

    private void handleAttachmentsForSubTaskByAttachType(final List<WorkSubTaskInfo> subTasks,
                                                         final LoginUserPojo user,
                                                         final MultipartHttpServletRequest multipartReq,
                                                         final AttachTypeEnum attachType,
                                      final BiConsumer<WorkSubTaskInfo, List<WorkApplyInfoAttachment>> setAttachmentFunction) {
        final Map<String, WorkApplyInfoAttachment> attachmentMap =
                getAttachmentsForSubTaskByAttachType(multipartReq, attachType, user, subTasks.get(0));

        subTasks.forEach(subTask ->
            Optional.ofNullable(attachmentMap.get(subTask.getId()))
                    .ifPresent(a -> setAttachmentFunction.accept(subTask, Lists.newArrayList(a)))
        );
    }

    /**
     * 获取附件对象Map.
     * <p>key：分任务id</p>
     * <p>value：附件</p>
     * @param type 类型
     * @param user 用户对象
     * @param subTaskInfo 分任务
     * @return 附件对象Map
     */
    private  Map<String, WorkApplyInfoAttachment> getAttachmentsForSubTaskByAttachType(final MultipartHttpServletRequest request,
                                                                                       final AttachTypeEnum type,
                                                                                       final LoginUserPojo user,
                                                                                       final WorkSubTaskInfo subTaskInfo) {
        final  Map<String, WorkApplyInfoAttachment> attachmentMap = new HashMap<>();
        try {
            // 遍历所有附件名
            for (final Iterator<String> fileParamNames = request.getFileNames(); fileParamNames.hasNext(); ) {
                final String fileParamName = fileParamNames.next();
                final MultipartFile multipartFile = request.getFile(fileParamName);
                // 找到以指定类型开头的附件名
                if (!fileParamName.startsWith(type.name()) || null == multipartFile) {
                    continue;
                }

                final WorkApplyInfoAttachment attachment = buildWorkApplyInfoAttachment(multipartFile, type, user);

                // 2022-11-23 上报附件需要与模板附件比对，判断是否一致
                // 2022-02-08 不需要合并上报附件时，不需要去检查上报文件是否和模版一致。
                if (AttachTypeEnum.RESULT == type && subTaskInfo.isAutoMergeAttachment()) {
                    final String checkInfo =
                            workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTaskInfo.getMainTaskId(), AttachTypeEnum.TEMPLATE)
                            .stream()
                            .findFirst()
                            .map(template -> ExcelUtils.checkAttachment(template, Lists.newArrayList(attachment)))
                            .orElse(null);
                    if (StringUtils.isNotBlank(checkInfo)) {
                        throw new WorkApplyException(checkInfo);
                    }
                }

                // 截取附件类型，剩下的就是对应的分任务id
                final String subTaskId = fileParamName.substring(type.name().length());
                // 退回重新上报的情况，不能新增，需要更新：
                workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTaskId, type)
                        .stream()
                        .findFirst()
                        .map(WorkApplyInfoAttachment::getId)
                        .ifPresent(attachment::setId);

                // 重要：把分任务id设置进去，以便后续附件和分任务对象做关联
                attachment.setSubTaskId(subTaskId);
                attachmentMap.put(subTaskId, attachment);

            }
        } catch (IOException e) {
            throw new WorkApplyException("获取附件信息时出错！", e);
        }
        return attachmentMap;
    }

    private static List<WorkApplyInfoAttachment> getAttachmentsByParamName(final MultipartHttpServletRequest request,
                                                                           final AttachTypeEnum type,
                                                                           final LoginUserPojo user) {
        final List<MultipartFile> fileRes = request.getFiles(type.name());
        if (CollectionUtils.isNotEmpty(fileRes)) {
            return fileRes.stream().filter(it -> !it.isEmpty()).map(it -> {
                try {
                    return buildWorkApplyInfoAttachment(it, type, user);
                } catch (IOException e) {
                    LOGGER.error("获取文件流失败，文件名称:{}", it.getOriginalFilename(), e);
                }
                return null;
            }).filter(Objects::nonNull).collect(Collectors.toList());
        }
        return Lists.newArrayList();
    }

    private static WorkApplyInfoAttachment buildWorkApplyInfoAttachment(final MultipartFile multipartFile,
                                                                        final AttachTypeEnum type,
                                                                        final LoginUserPojo user) throws IOException {
        final WorkApplyInfoAttachment attachment = new WorkApplyInfoAttachment();
        attachment.setAttachment(multipartFile.getBytes());
        attachment.setUploadTime(DateTime.now().toDate());
        attachment.setType(type);
        attachment.setUploaderUid(user.getUserID());
        attachment.setUploaderName(user.getUserName());
        attachment.setFileName(multipartFile.getOriginalFilename());
        return attachment;
    }

    /**
     * 插入分任务附件。
     *
     * @param workSubTaskInfo 分任务
     */
    @Override
    public void insertAttachmentForSubTask(final WorkSubTaskInfo workSubTaskInfo) {
        insertTemplateAttachment(workSubTaskInfo);
        insertMergeAttachment(workSubTaskInfo);
        insertReportAttachment(workSubTaskInfo);
    }

    /**
     * 插入模板附件.
     *
     * @param workSubTaskInfo 分任务
     */
    public void insertTemplateAttachment(WorkSubTaskInfo workSubTaskInfo) {
        insertAttachmentByType(workSubTaskInfo, AttachTypeEnum.TEMPLATE,WorkSubTaskInfo::getTemplateAttachments);
    }

    /**
     * 插入合并附件.
     *
     * @param workSubTaskInfo 分任务
     */
    public void insertMergeAttachment(WorkSubTaskInfo workSubTaskInfo) {
        insertAttachmentByType(workSubTaskInfo, AttachTypeEnum.MERGE,WorkSubTaskInfo::getMergeAttachments);
    }

    /**
     * 插入上报附件.
     *
     * @param workSubTaskInfo 分任务
     */
    public void insertReportAttachment(WorkSubTaskInfo workSubTaskInfo) {
        insertAttachmentByType(workSubTaskInfo, AttachTypeEnum.RESULT,WorkSubTaskInfo::getReportAttachments);
    }

    private void insertAttachmentByType(final WorkSubTaskInfo workSubTaskInfo,
                                        final AttachTypeEnum attachTypeEnum,
                                        final Function<WorkSubTaskInfo, List<WorkApplyInfoAttachment>> getAttachmentFunction) {
        List<WorkApplyInfoAttachment> attachmentList = getAttachmentFunction.apply(workSubTaskInfo);
        if(CollectionUtils.isEmpty(attachmentList)) {
            return;
        }
        for(WorkApplyInfoAttachment attachment : attachmentList) {
            // 有id的，说明是上次保存的时候上传的附件，本次即不需要更新、也不需要插入或删除，保持现状即可
            if (StringUtils.isBlank(attachment.getId())) {
                attachment.setId(UUID.randomUUID().toString());
                attachment.setWorkApplyId(workSubTaskInfo.getWorkApplyId());
                attachment.setSubTaskId(workSubTaskInfo.getId());
                attachment.setType(attachTypeEnum);
                workApplyInfoMapper.insertWorkApplyInfoAttachment(attachment);
            } else if(attachTypeEnum == AttachTypeEnum.MERGE) {
                // 只有合并附件需要更新，其他附件都是不需要更新
                workApplyInfoMapper.updateWorkApplyInfoAttachment(attachment);
            }
        }
    }

    /**
     * 根据id查询附件信息.
     *
     * @param attachmentId 附件主键id
     * @return 附件对象
     */
    @Override
    public WorkApplyInfoAttachment findWorkAttachmentById(String attachmentId) {
        return workApplyInfoMapper.findWorkApplyInfoAttachmentsById(attachmentId);
    }

    /**
     * 删除附件。
     *
     * @param id 附件id
     */
    @Override
    public void deleteAttachmentById(final String id) {
        workApplyInfoMapper.deleteAttachmentById(id);
    }

    /**
     * 工单级的合并结果附件操作。
     * <p>将子工单的结果附件（也叫上报附件）合并到父工单的合并附件</p>
     * <p>对子工单而言，是整体合并，也即子工单的所有分任务的都要合并到父工单的分任务里</p>
     * <p>合并好的合并附件挂载到父工单对象里，不需要返回</p>
     *
     * @param info                   父工单
     * @param needFinishSubApplyList 需要合并的子工单列表
     */
    @Override
    public void mergeResultAttachmentContentForWorkApply(final WorkApplyInfo info,
                                                         final List<WorkApplyInfo> needFinishSubApplyList) {
        final List<WorkApplyInfoAttachment> mergeAttachments = info.getMergeAttachments();
        if (CollectionUtils.isEmpty(needFinishSubApplyList) || CollectionUtils.isEmpty(mergeAttachments)) {
            return;
        }

        final List<WorkApplyInfoAttachment> sourceContents = Lists.newArrayList();
        for (final WorkApplyInfo subApply : needFinishSubApplyList) {
            final List<WorkApplyInfoAttachment> sourceAttachs = workApplyInfoMapper
                    .findWorkApplyInfoAttachmentsByApplyId(subApply.getId(), AttachTypeEnum.RESULT);
            Preconditions.checkArgument(CollectionUtils.isNotEmpty(sourceAttachs),
                    "分任务的结果附件为空！分任务id:" + subApply.getId());
            CollectionUtils.addAll(sourceContents, sourceAttachs.iterator());
        }
        for (final WorkApplyInfoAttachment mergeAttachment : mergeAttachments) {
            final int size = mergeAttachment.getAttachment().length;
            LOGGER.info("sourceContents:{}", sourceContents);
            ExcelUtils.writeContentToFile(mergeAttachment, sourceContents);
            LOGGER.info("============merge attachment size:{} ,after:{}", size, mergeAttachment.getAttachment().length);
            this.workApplyInfoMapper.updateWorkApplyInfoAttachment(mergeAttachment);
        }
    }

    /**
     * 分任务级的合并结果附件操作。
     * <p>将分任务的结果附件（也叫上报附件）合并到父级分任务的合并附件</p>
     * <p>合并好的合并附件挂载到父级分任务对象里，不需要返回</p>
            *
            * @param parentSubTaskInfo    父级分任务
     * @param confirmedSubTaskList 已经被确认的分任务，也即需要合并的下级分任务
     */
    @Override
    public void mergeResultAttachmentContentForSubTask(final WorkSubTaskInfo parentSubTaskInfo,
                                                       final List<WorkSubTaskInfo> confirmedSubTaskList) {
        // 附件可以为空了,为空不合并
        if (CollectionUtils.isEmpty(parentSubTaskInfo.getTemplateAttachments())) {
            return;
        }

        // 只合并第一个
        final WorkApplyInfoAttachment mergeAttachment = getOrReloadAttachment(parentSubTaskInfo, AttachTypeEnum.MERGE);

        final WorkApplyInfoAttachment templateAttachment = getOrReloadAttachment(parentSubTaskInfo, AttachTypeEnum.TEMPLATE);

        // 把所有下级分任务的上报附件放到一个List里去
        final List<WorkApplyInfoAttachment> reportAttachmentList = confirmedSubTaskList.stream()
                .map(WorkSubTaskInfo::getReportAttachments)
                .flatMap(Collection::stream)
                // 判断是否需要清除处理人（excel中不显示处理人）
                .peek(reportAttachment -> {
                    if (parentSubTaskInfo.isAutoMergeAttachment() && !parentSubTaskInfo.isRemoveHandler()) {
                        reportAttachment.setUploaderName(null);
                    }
                })
                .collect(Collectors.toList());

        //存在模板附件且需要自动合并附件才合并
        if (parentSubTaskInfo.isAutoMergeAttachment()) {
            ExcelUtils.writeToResultContent(templateAttachment, reportAttachmentList, mergeAttachment);
        }
    }

    /**
     * 获取或者重新加载附件信息
     * @param workSubTaskInfo 分任务信息
     * @param typeEnum 附件类型
     * @return 附件信息
     */
    private WorkApplyInfoAttachment getOrReloadAttachment(final WorkSubTaskInfo workSubTaskInfo, final AttachTypeEnum typeEnum) {
        final WorkApplyInfoAttachment attachment;
        switch (typeEnum) {
            case MERGE:
                attachment = workSubTaskInfo.getMergeAttachments().get(0);
                break;
            case RESULT:
                attachment = workSubTaskInfo.getReportAttachments().get(0);
                break;
            default:
                attachment = workSubTaskInfo.getTemplateAttachments().get(0);
        }

        // 如果附件内容为空需要重新去数据库查询
        final byte[] attachmentContent = Optional.ofNullable(attachment)
                .map(WorkApplyInfoAttachment::getAttachment)
                .orElseGet(() -> getAttachmentData(workSubTaskInfo, typeEnum));
        attachment.setAttachment(attachmentContent);

        return attachment;
    }

    private byte[] getAttachmentData(final WorkSubTaskInfo workSubTaskInfo, final AttachTypeEnum typeEnum) {
        String id = typeEnum == AttachTypeEnum.TEMPLATE && StringUtils.isNotBlank(workSubTaskInfo.getMainTaskId())
                ? workSubTaskInfo.getMainTaskId()
                : workSubTaskInfo.getId();
        final List<WorkApplyInfoAttachment> attachmentsFromDb =
                workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(id, typeEnum);
        if (CollectionUtils.isNotEmpty(attachmentsFromDb)) {
            return attachmentsFromDb.get(0).getAttachment();
        }
        return new byte[0];
    }
}
